import Decimal from 'decimal.js'

type Option = {
    originVal?: boolean
}

type Values = (number | string | object)[]

type Types = 'add' | 'sub' | 'mul' | 'div'

export function operate (type: Types, values: Values, option: Option = {}) {
    if (values.length < 2) return values?.[0] ?? null
    let i = 2
    let init = Number(values[i - 2])
    if (isNaN(init)) {
        return new TypeError(`${i - 2} of values can not be transformed to number`)
    }
    let result = new Decimal(init)
    while (i <= values.length) {
        let next = Number(values[i - 1])
        if (isNaN(next)) {
            return new TypeError(`${i - 1} of values can not be transformed to number`)
        }
        result = result[type](new Decimal(next))
        i++
    }
    return option.originVal ? result : result.toNumber()
}

// 加
export function add (values: Values, option: Option = {}) {
    return operate('add', values, option)
}

// 减
export function sub (values: Values, option: Option = {}) {
    return operate('sub', values, option)
}

// 乘
export function mul (values: Values, option: Option = {}) {
    return operate('mul', values, option)
}

// 除
export function div (values: Values, option: Option = {}) {
    return operate('div', values, option)
}

// 数学表达式
export function evaluateExpression (expression: string) {
    const operators = {
        '+': { precedence: 1, method: add },
        '-': { precedence: 1, method: sub },
        '*': { precedence: 2, method: mul },
        '/': { precedence: 2, method: div }
    }
  
    function applyOperator (operator, operandStack) {
        const rightOperand = operandStack.pop()
        const leftOperand = operandStack.pop()
        const result = operators[operator].method([leftOperand, rightOperand])
        operandStack.push(result)
    }
  
    function isOperator (token) {
        return token in operators
    }
  
    const tokens = expression.match(/(\d+(\.\d+)?|\+|\-|\*|\/|\(|\))/g) as RegExpMatchArray
    const operandStack: number[] = []
    const operatorStack: string[] = []
  
    for (const token of tokens) {
        if (/^\d+(\.\d+)?$/.test(token)) {
            operandStack.push(Number(token))
        } else if (isOperator(token)) {
            while (
                operatorStack.length > 0 &&
          isOperator(operatorStack[operatorStack.length - 1]) &&
          operators[token].precedence <= operators[operatorStack[operatorStack.length - 1]].precedence
            ) {
                applyOperator(operatorStack.pop(), operandStack)
            }
            operatorStack.push(token)
        } else if (token === '(') {
            operatorStack.push(token)
        } else if (token === ')') {
            while (operatorStack.length > 0 && operatorStack[operatorStack.length - 1] !== '(') {
                applyOperator(operatorStack.pop(), operandStack)
            }
            if (operatorStack.length === 0) {
                throw new Error('Mismatched parentheses')
            }
            operatorStack.pop()
        } else {
            throw new Error('Invalid token: ' + token)
        }
    }
  
    while (operatorStack.length > 0) {
        if (operatorStack[operatorStack.length - 1] === '(') {
            throw new Error('Mismatched parentheses')
        }
        applyOperator(operatorStack.pop(), operandStack)
    }
  
    if (operandStack.length !== 1 || operatorStack.length !== 0) {
        throw new Error('Invalid expression')
    }
  
    return operandStack[0]
}

function install (Vue) {
    Vue.prototype.$mgAdd = add
    Vue.prototype.$mgSub = sub
    Vue.prototype.$mgMul = mul
    Vue.prototype.$mgDiv = div
    Vue.prototype.$decimal = operate
    Vue.prototype.$mgCalc = evaluateExpression
}

export default install
